import json
import logging
import os
import sys
import threading
import time
import traceback
from configparser import ConfigParser
from time import sleep
import global_path

from loguru import logger

from binance.error import ClientError
from binance.um_futures import UMFutures
from binance.websocket.um_futures.websocket_client import UMFuturesWebsocketClient
from running import utils
from running.account import Account
from running.position import Position
from running.symbol import Symbol, STATUS_NO_POSITION, STATUS_UNKNOWN_ERROR, STATUS_PENDING_OPENING_ORDER, \
    STATUS_HOLDING

logger.add("quant.txt", filter=lambda record: record["extra"]["name"] == "service_log", rotation="00:00")
logger.add("record.txt", filter=lambda record: record["extra"]["name"] == "app_log", rotation="00:00")
info_logger = logger.bind(name="service_log")
record_logger = logger.bind(name="app_log")

# ticker间隔
INTERVAL_S = 1
# 更新持仓信息间隔
UPDATE_POSITION_INTERVAL = 1
# 更新余额信息间隔
UPDATE_BALANCE_INTERVAL = 1
# 缓存文件
SESSION_FILE = "./session.txt"
EXCHANGE_FILE = "./exchange.txt"
STOP_FILE = "./stop.txt"
# RECORD EVENT
EVENT_OPEN = "OPEN"
EVENT_ADD = "ADD"
EVENT_CLOSE_WIN = "CLOSE_WIN"
EVENT_CLOSE_LOSE = "CLOSE_LOSE"
# 配置文件
CONFIG_FILE = "quant-config.cfg"
# 检查状态，超过1分钟不对则重连
RECONNECT_REIGGER_COUNT = 20
TRUE = "True"
FALSE = "False"

SEND_MSG_INTERVAL = 3600
WAIT_AFTER_CLOSE = 10
# 每300个ticker重启一次，即10分钟，防止有些信号没有收到的情况
RESTART_TICKER_COUNT = 864000
STATUS_INTERVAL = 30 * 60
STATUS_QUOTE_VOLUME = 200000000
UPDATE_SERVERTIME_INTERNAL = 60

REFRESH_ORDER_INTERNAL = 60


class Quant:
    def __init__(self):
        self.lock = threading.Lock()
        # 持仓dict
        self.positions_long = {}
        self.positions_short = {}
        # 当前委托
        self.orders = {}
        # 价格表
        self.symbol_price = {}
        self.account = Account()
        self.use_proxy = False
        self.proxy = None
        self.ticker_count = 0
        self.servertime = 0
        self.symbol = Symbol()
        self.last_kline_ts = 0
        self.op_history = []
        self.op_history_long = {}
        self.op_history_short = {}
        self.orders = {}
        self.position = None
        self.last_refresh_order_ts = 0
        self.usdt_balance = 0
        self.open_times = 0
        self.lose_times = 0
        self.win_times = 0

    # 初始化
    def on_init(self):
        info_logger.warning("********** quant init **********")

        # 读取配置文件
        self.read_configs()

        self.init_clients()

        # self.read_precision()

        self.subscribe_and_read()

        self.init_accounts()

        now = int(time.time())
        record_logger.info(f"开始运行 {utils.timestamp_to_string(now)} 余额:{self.usdt_balance}")
        # self.init_martins()
        self.last_init_martin_time = now

        # self.update_symbol_status()
        info_logger.warning("running on %s,%s" % (self.account.account, self.account.g_api_key))

    def init_accounts(self):
        try:
            balance_list = self.um_futures_client.balance(recvWindow=6000)
            for b in balance_list:
                if b['asset'] == "USDT":
                    self.usdt_balance = float(b['balance'])
            logging.info(balance_list)
        except ClientError as error:
            logging.error(
                "Found error. status: {}, error code: {}, error message: {}".format(
                    error.status_code, error.error_code, error.error_message
                )
            )

    def read_precision(self):
        ei = self.um_futures_client.exchange_info()
        for e in ei['symbols']:
            if e['symbol'] == self.symbol.trade_symbol:
                self.symbol.price_precision = e['pricePrecision']
                self.symbol.quantity_precision = e['quantityPrecision']

    def read_configs(self):
        cp = ConfigParser()
        cp.read(CONFIG_FILE)
        self.account.account = cp.get("quant", "account")
        self.account.g_api_key = cp.get("quant", "g_api_key")
        self.account.g_secret_key = cp.get("quant", "g_secret_key")
        self.use_proxy = cp.getboolean("quant", "use_proxy")
        self.proxy = eval(cp.get("quant", "proxy"))

        self.symbol.trade_symbol = cp.get("symbol", "symbol")
        self.symbol.period = cp.get("symbol", "period")
        self.symbol.max_breakout_length = cp.getint("symbol", "max_breakout_length")
        self.symbol.threshold_rate = cp.getfloat("symbol", "threshold_rate")
        self.symbol.min_number_of_test = cp.getint("symbol", "min_number_of_test")
        self.symbol.pivot_stage = cp.getint("symbol", "pivot_stage")
        self.symbol.profit_ratio = cp.getint("symbol", "profit_ratio")
        self.symbol.stop_lose_ratio = cp.getint("symbol", "stop_lose_ratio")
        self.symbol.position_amount = cp.getfloat("symbol", "position_amount")
        self.symbol.leverage = cp.getint("symbol", "leverage")

        self.symbol.price_precision = cp.getint("symbol", "price_precision")
        self.symbol.quantity_precision = cp.getint("symbol", "quantity_precision")
        self.symbol.tick_price_precision = cp.getint("symbol", "tick_price_precision")

        self.symbol.min_qty = cp.getfloat("symbol", "min_qty")

    def init_clients(self):
        # UMFutures
        if self.use_proxy:
            self.um_futures_client = UMFutures(key=self.account.g_api_key, secret=self.account.g_secret_key,
                                               proxies=self.proxy)
        else:
            self.um_futures_client = UMFutures(key=self.account.g_api_key, secret=self.account.g_secret_key)

        # UMFuturesWebsocketClient
        if self.use_proxy:
            self.um_futures_ws_client = UMFuturesWebsocketClient(on_message=self.umFuturesWebsocketClientHandler,
                                                                 proxies=self.proxy)
        else:
            self.um_futures_ws_client = UMFuturesWebsocketClient(on_message=self.umFuturesWebsocketClientHandler)

    def subscribe_and_read(self):
        self.subscribe()

    def subscribe(self):
        try:
            info_logger.warning("subscribe")
            # UserData
            response = self.um_futures_client.new_listen_key()
            self.um_futures_ws_client.user_data(
                listen_key=response["listenKey"],
                id=1,
            )

            # ticker
            self.um_futures_ws_client.mini_ticker(id=2, symbol=self.symbol.trade_symbol)
        except Exception as e:
            info_logger.warning("subscribe_userdata error")
            info_logger.warning(str(e))
            return None

    # 数组中是否有重复元素
    def is_duplicate_array(self, symbols):
        return len(symbols) != len(set(symbols))

    def update_symbol_status(self, symbol):
        # info_logger.warning("********** update_symbol_status **********")
        klines = self.um_futures_client.klines(limit=symbol.max_breakout_length * 2 + 10,
                                               symbol=symbol.trade_symbol,
                                               interval=symbol.period)
        symbol.current_kline = klines[-1]

        this_kline_ts = klines[-1][0]
        # info_logger.warning(f"********** this_kline_ts：{utils.timestamp_to_string(this_kline_ts/1000)}  "
        #                f"last_kline_ts:{utils.timestamp_to_string(self.last_kline_ts/1000)} **********")
        if symbol.last_kline_ts == this_kline_ts:
            return

        info_logger.warning("********** DO update_symbol_status **********")
        symbol.last_kline_ts = this_kline_ts
        symbol.pivot_high = []
        symbol.pivot_low = []
        start = len(klines) - symbol.max_breakout_length - 1
        end = len(klines)

        high = 0
        low = 9999999
        # 先计算最高点和最低点，计算chwidth
        for i in range(start, end):
            kline = klines[i]
            high = max(high, float(kline[2]))
            low = min(low, float(kline[3]))
        chwidth = round((high - low) * self.symbol.threshold_rate, symbol.price_precision)

        # 再遍历每根k线，看是不是pivothigh/pivotlow
        # 靠近两侧的不算pivot
        for i in range(start + symbol.pivot_stage, end - symbol.pivot_stage):
            kline = klines[i]
            is_pivot_high = True
            is_pivot_low = True
            kline_t = kline[0]
            kline_open = float(kline[1])
            kline_high = float(kline[2])
            kline_low = float(kline[3])
            kline_close = float(kline[4])
            left = max(start, i - symbol.pivot_stage)
            right = min(end, i + symbol.pivot_stage)
            for ii in range(left, right):
                compare_kline = klines[ii]
                compare_high = float(compare_kline[2])
                compare_low = float(compare_kline[3])
                # 跳过自己
                if compare_kline[0] == kline[0]:
                    continue
                if compare_high > kline_high:
                    is_pivot_high = False
                if compare_low < kline_low:
                    is_pivot_low = False
                if not is_pivot_high and not is_pivot_low:
                    break

            if is_pivot_high:
                # # 每根k线单独计算chwidth
                # hgst = 0
                # lwst = 9999999
                # for j in range(i - symbol.max_breakout_length, i):
                #     kl = klines[j]
                #     hgst = max(hgst, float(kl[2]))
                #     lwst = min(lwst, float(kl[3]))
                # chwidth = round((hgst - lwst) * symbol.threshold_rate, symbol.price_precision)

                # info_logger.warning(
                #     f"pivot high {kline_t} {utils.timestamp_to_string(kline_t / 1000)} h:{kline_high} l:{kline_low}")

                # 如果和前一个pivot高点相同，则把前一个换成现在这个
                if len(symbol.pivot_high) > 0 and symbol.pivot_high[-1]['h'] == kline_high:
                    symbol.pivot_high[-1] = {"index": i, "t": kline_t, "h": kline_high, "l": kline_low,
                                             "o": kline_open, "c": kline_close, "chwidth": chwidth,
                                             "low_band": round(kline_high - chwidth, symbol.price_precision),
                                             "test_times": 0, "is_break": False}
                else:
                    symbol.pivot_high.append(
                        {"index": i, "t": kline_t, "h": kline_high, "l": kline_low,
                         "o": kline_open, "c": kline_close, "chwidth": chwidth,
                         "low_band": round(kline_high - chwidth, symbol.price_precision),
                         "test_times": 0, "is_break": False})

            if is_pivot_low:
                # #每根k线单独计算chwidth
                # hgst = 0
                # lwst = 9999999
                # for j in range(i - symbol.max_breakout_length, i):
                #     kl = klines[j]
                #     hgst = max(hgst, float(kl[2]))
                #     lwst = min(lwst, float(kl[3]))
                # chwidth = round((hgst - lwst) * symbol.threshold_rate, symbol.price_precision)
                # info_logger.warning(
                #     f"pivot low {kline_t} {utils.timestamp_to_string(kline_t / 1000)} h:{kline_high} l:{kline_low}")
                # 如果和前一个pivot低点相同，则把前一个换成现在这个
                if len(symbol.pivot_low) > 0 and symbol.pivot_low[-1]['l'] == kline_low:
                    symbol.pivot_low[-1] = {"index": i, "t": kline_t, "h": kline_high, "l": kline_low,
                                            "o": kline_open, "c": kline_close, "chwidth": chwidth,
                                            "high_band": round(kline_low + chwidth, symbol.price_precision),
                                            "test_times": 0, "is_break": False}
                else:
                    symbol.pivot_low.append(
                        {"index": i, "t": kline_t, "h": kline_high, "l": kline_low,
                         "o": kline_open, "c": kline_close, "chwidth": chwidth,
                         "high_band": round(kline_low + chwidth, symbol.price_precision),
                         "test_times": 0, "is_break": False})

        # 再遍历每个pivot point,计算test times以及是否突破
        for i in range(0, len(symbol.pivot_high)):
            ph = symbol.pivot_high[i]
            h = ph["h"]
            chwidth = ph['chwidth']
            # if ph['h'] == 83230.6:
            #     print()
            for k in range(ph['index'] + 1, end):
                compare_high = round(float(klines[k][2]), symbol.price_precision)
                if compare_high > h:
                    ph['is_break'] = True
                    break
            # 先比较左边
            for j in range(1, i + 1):
                # i-1 to 0
                left_pivot_index = i - j
                compare_kline = symbol.pivot_high[left_pivot_index]
                compare_high = compare_kline['h']
                compare_low = compare_kline['l']
                compare_open = compare_kline['o']
                compare_close = compare_kline['c']

                if compare_kline['h'] > ph['h']:
                    break
                if h - chwidth <= compare_high <= h and (ph['index'] - compare_kline['index'] > symbol.pivot_stage):
                    ph["test_times"] = ph["test_times"] + 1
                    minutes_between_pivot_and_compare = round((compare_kline['t'] / 1000 - ph['t'] / 1000) / 60,
                                                              symbol.price_precision)
                    minutes_between_compare_and_current = round((this_kline_ts / 1000 - compare_kline['t'] / 1000) / 60,
                                                                symbol.price_precision)
                    info_logger.warning(
                        f"increase high test times {utils.timestamp_to_string(ph['t'] / 1000)} <==> {utils.timestamp_to_string(compare_kline['t'] / 1000)}  p-c:{minutes_between_pivot_and_compare} c-n:{minutes_between_compare_and_current}")

            # 再比较右边
            for j in range(i + 1, len(symbol.pivot_high)):
                compare_kline = symbol.pivot_high[j]
                if ph['is_break']:
                    continue
                compare_high = compare_kline['h']
                compare_low = compare_kline['l']
                compare_open = compare_kline['o']
                compare_close = compare_kline['c']
                if h - chwidth <= compare_high <= h and (end - compare_kline['index'] > symbol.pivot_stage):
                    ph["test_times"] = ph["test_times"] + 1
                    minutes_between_pivot_and_compare = round((compare_kline['t'] / 1000 - ph['t'] / 1000) / 60,
                                                              symbol.price_precision)
                    minutes_between_compare_and_current = round((this_kline_ts / 1000 - compare_kline['t'] / 1000) / 60,
                                                                symbol.price_precision)
                    info_logger.warning(
                        f"increase high test times {utils.timestamp_to_string(ph['t'] / 1000)} <==> {utils.timestamp_to_string(compare_kline['t'] / 1000)}  p-c:{minutes_between_pivot_and_compare} c-n:{minutes_between_compare_and_current}")

                if compare_high > h:
                    ph["is_break"] = True
                    compare_kline_index = compare_kline['index']
                    ph_index = ph['index']
                    current_index = len(klines) - 1
                    logging.warning(
                        f"break high {utils.timestamp_to_string(ph['t'] / 1000)} {h}  <==>  {utils.timestamp_to_string(compare_kline['t'] / 1000)}"
                        f" {compare_high} {compare_kline_index - ph_index} {current_index - compare_kline_index}")
                    break

        for ph in symbol.pivot_high:
            info_logger.warning(
                f"{utils.timestamp_to_string(ph['t'] / 1000)} h:{ph['h']} l:{ph['l']} chwidth:{ph['chwidth']} low_band:{ph['low_band']} test:{ph['test_times']} is_break:{ph['is_break']}")

        # 再遍历每个pivot point,计算test times以及是否突破
        for i in range(0, len(symbol.pivot_low)):
            pl = symbol.pivot_low[i]
            l = pl["l"]
            chwidth = pl['chwidth']
            # if pl['l'] == 81755.0:
            #     print()
            for k in range(pl['index'] + 1, end):
                compare_low = round(float(klines[k][3]), symbol.price_precision)
                if compare_low < l:
                    pl['is_break'] = True
                    break
            # 先比较左边
            for j in range(1, i + 1):
                # i-1 to 0
                left_pivot_index = i - j
                compare_kline = symbol.pivot_low[left_pivot_index]
                compare_high = compare_kline['h']
                compare_low = compare_kline['l']
                compare_open = compare_kline['o']
                compare_close = compare_kline['c']

                if compare_low < pl['l']:
                    break
                if l + chwidth >= compare_low >= l and (pl['index'] - compare_kline['index'] > symbol.pivot_stage):
                    pl["test_times"] = pl["test_times"] + 1
                    minutes_between_pivot_and_compare = round((compare_kline['t'] / 1000 - pl['t'] / 1000) / 60, 2)
                    minutes_between_compare_and_current = round(
                        (this_kline_ts / 1000 - compare_kline['t'] / 1000) / 60, symbol.price_precision)
                    info_logger.warning(
                        f"increase low test times {utils.timestamp_to_string(pl['t'] / 1000)} <==> {utils.timestamp_to_string(compare_kline['t'] / 1000)}  p-c:{minutes_between_pivot_and_compare} c-n:{minutes_between_compare_and_current}")

            # 再比较右边
            for j in range(i + 1, len(symbol.pivot_low)):
                compare_kline = symbol.pivot_low[j]
                if pl['is_break']:
                    continue
                compare_high = compare_kline['h']
                compare_low = compare_kline['l']
                compare_open = compare_kline['o']
                compare_close = compare_kline['c']
                if l + chwidth >= compare_low >= l and (end - compare_kline['index'] > symbol.pivot_stage):
                    pl["test_times"] = pl["test_times"] + 1
                    minutes_between_pivot_and_compare = round((compare_kline['t'] / 1000 - pl['t'] / 1000) / 60, 2)
                    minutes_between_compare_and_current = round(
                        (this_kline_ts / 1000 - compare_kline['t'] / 1000) / 60, symbol.price_precision)
                    info_logger.warning(
                        f"increase low test times {utils.timestamp_to_string(pl['t'] / 1000)} <==> {utils.timestamp_to_string(compare_kline['t'] / 1000)}  p-c:{minutes_between_pivot_and_compare} c-n:{minutes_between_compare_and_current}")

                if compare_low < l:
                    pl["is_break"] = True
                    compare_kline_index = compare_kline['index']
                    pl_index = pl['index']
                    current_index = len(klines) - 1
                    logging.warning(
                        f"break low {utils.timestamp_to_string(pl['t'] / 1000)} {l}  <==>  {utils.timestamp_to_string(compare_kline['t'] / 1000)} {compare_low}"
                        f" {compare_kline_index - pl_index} {current_index - compare_kline_index}")
                    break
        for pl in symbol.pivot_low:
            info_logger.warning(
                f"{utils.timestamp_to_string(pl['t'] / 1000)} h:{pl['h']} l:{pl['l']} chwidth:{pl['chwidth']} high_band:{pl['high_band']} test:{pl['test_times']} is_break:{pl['is_break']}")

    def get_breaking_pivots(self, symbol):
        breaking_ph_list = []
        breaking_pl_list = []
        for ph in symbol.pivot_high:
            if ph['is_break']:
                continue
            open = float(symbol.current_kline[1])
            high = float(symbol.current_kline[2])
            low = float(symbol.current_kline[3])
            close = float(symbol.current_kline[4])
            if ph['test_times'] >= symbol.min_number_of_test and (close > ph['h'] or high > ph['h']):
                info_logger.warning(
                    f"pivot high is break "
                    f"{utils.timestamp_to_string(ph['t'] / 1000)} "
                    f"h:{ph['h']} l:{ph['l']} low_band:{ph['low_band']} "
                    f"test:{ph['test_times']} is_break:{ph['is_break']} current h:{high} close:{close}")
                # self.exit()
                width = close - ph['low_band']
                breaking_ph_list.append(ph)
                # self.open_long(ph['t'], ph['h'], ph['h'] - self.symbol.stop_lose_ratio * width,
                #                ph['h'] + self.symbol.profit_ratio * width)

            if ph['test_times'] < symbol.min_number_of_test and (close > ph['h'] or high > ph['h']):
                ph['is_break'] = True
                info_logger.warning(
                    f"pivot high is break no enough test "
                    f"{utils.timestamp_to_string(ph['t'] / 1000)} "
                    f"h:{ph['h']} l:{ph['l']} low_band:{ph['low_band']} "
                    f"test:{ph['test_times']} is_break:{ph['is_break']}  current h:{high} close:{close}")
                # self.exit()

        # 当有多个break的时候，只用第一个，越前面越有效
        if len(breaking_ph_list) > 1:
            breaking_ph_list = [breaking_ph_list[0]]

        for pl in symbol.pivot_low:
            if pl['is_break']:
                continue
            open = float(symbol.current_kline[1])
            high = float(symbol.current_kline[2])
            low = float(symbol.current_kline[3])
            close = float(symbol.current_kline[4])
            if pl['test_times'] >= symbol.min_number_of_test and (close < pl['l'] or low < pl['l']):
                info_logger.warning(
                    f"pivot low is break "
                    f"{utils.timestamp_to_string(pl['t'] / 1000)} "
                    f"h:{pl['h']} l:{pl['l']} high_band:{pl['high_band']} "
                    f"test:{pl['test_times']} is_break:{pl['is_break']}  current l:{low} close:{close}")
                # self.exit()
                width = pl['high_band'] - close
                # self.open_short(pl['t'], pl['l'], pl['l'] + self.symbol.stop_lose_ratio * width,
                #                 pl['l'] - self.symbol.profit_ratio * width)
                breaking_pl_list.append(pl)

            if pl['test_times'] < symbol.min_number_of_test and (close < pl['l'] or low < pl['l']):
                pl['is_break'] = True
                info_logger.warning(
                    f"pivot low is break no enough test "
                    f"{utils.timestamp_to_string(pl['t'] / 1000)} "
                    f"h:{pl['h']} l:{pl['l']} high_band:{pl['high_band']} "
                    f"test:{pl['test_times']} is_break:{pl['is_break']}  current l:{low} close:{close}")
                # self.exit()
        # 当有多个break的时候，只用第一个，越前面越有效
        if len(breaking_pl_list) > 1:
            breaking_pl_list = [breaking_pl_list[0]]

        # if len(symbol.pivot_high):
        #     breaking_ph_list.append(symbol.pivot_high[-1])
        # #
        # if len(symbol.pivot_low):
        #     breaking_pl_list.append(symbol.pivot_low[-1])
        return breaking_ph_list, breaking_pl_list

    def open_long(self, t, price, stop_lose_price, stop_win_price):
        for o in self.op_history:
            strs = o.split("=")
            str_t = utils.timestamp_to_string(t / 1000)
            if str_t == strs[2]:
                return
        self.op_history.append(
            f"buy={utils.timestamp_to_string(self.servertime / 1000)}={utils.timestamp_to_string(t / 1000)}={price}={stop_lose_price}={stop_win_price}")

    def open_short(self, t, price, stop_lose_price, stop_win_price):
        for o in self.op_history:
            strs = o.split("=")
            str_t = utils.timestamp_to_string(t / 1000)
            if str_t == strs[2]:
                return
        self.op_history.append(
            f"sell={utils.timestamp_to_string(self.servertime / 1000)}={utils.timestamp_to_string(t / 1000)}={price}={stop_lose_price}={stop_win_price}")

    def on_ticker(self):
        info_logger.warning(
            f"ticker count:{self.ticker_count} server time:{utils.timestamp_to_string(self.servertime / 1000)}")
        # if self.close_wrong_positions():
        #     return
        self.log_status()
        if self.has_stop_file():
            self.kill()
            return

        self.update_server_time()
        self.refresh_orders()
        self.roll_symbol_status(self.symbol)
        self.ticker_count += 1

    def roll_symbol_status(self, symbol):
        if symbol.status == STATUS_NO_POSITION:
            self.handle_no_position(symbol)
        elif symbol.status == STATUS_PENDING_OPENING_ORDER:
            self.handle_pending_opening_order(symbol)
        elif symbol.status == STATUS_HOLDING:
            self.handle_holding(symbol)
        elif symbol.status == STATUS_UNKNOWN_ERROR:
            self.handle_unknown_error(symbol)

    def handle_no_position(self, symbol):
        info_logger.warning("========== handle_no_position ==========")
        if self.symbol.current_price == 0:
            return
        self.update_symbol_status(symbol)
        breaking_ph_list, breaking_pl_list = self.get_breaking_pivots(symbol)

        for ph in breaking_ph_list:
            # def open_long(self, t, price, stop_lose_price, stop_win_price):
            # self.open_long()
            label = utils.timestamp_to_string(ph['t'] / 1000)
            if label not in self.op_history_long.keys():
                chwidth = ph['chwidth']
                take_profit_price = str(
                    round(symbol.current_price + chwidth * symbol.profit_ratio, symbol.tick_price_precision))
                stop_lose_price = str(
                    round(symbol.current_price - chwidth * symbol.stop_lose_ratio, symbol.tick_price_precision))
                quantity = str(
                    round(max(symbol.position_amount / (chwidth * symbol.stop_lose_ratio), symbol.min_qty),
                          symbol.quantity_precision))
                position_side = "LONG"

                symbol.take_profit_price = take_profit_price
                symbol.stop_lose_price = stop_lose_price
                symbol.quantity = quantity
                symbol.position_side = position_side

                self.change_leverage(symbol.trade_symbol, symbol.leverage)
                open_order_id = self.open_position_market(symbol.trade_symbol, position_side, quantity)
                o = {'X': "NEW"}
                self.orders[open_order_id] = o
                self.open_times += 1
                self.update_balance()
                self.log_open(symbol)
                symbol.open_order_id = open_order_id
                symbol.status = STATUS_PENDING_OPENING_ORDER
                info_logger.warning(f"status==>STATUS_PENDING_OPENING_ORDER")
                self.op_history_long[label] = f"buy symbol:{symbol.trade_symbol} p:{symbol.current_price} " \
                                              f"qty:{symbol.quantity} stop:{symbol.stop_lose_price} profit:{symbol.take_profit_price} " \
                                              f"t:{utils.timestamp_to_string(self.servertime / 1000)} break:{utils.timestamp_to_string(ph['t'] / 1000)}"

        for pl in breaking_pl_list:
            label = utils.timestamp_to_string(pl['t'] / 1000)
            if label not in self.op_history_short.keys():
                chwidth = pl['chwidth']
                take_profit_price = str(
                    round(symbol.current_price - chwidth * symbol.profit_ratio, symbol.tick_price_precision))
                stop_lose_price = str(
                    round(symbol.current_price + chwidth * symbol.stop_lose_ratio, symbol.tick_price_precision))
                quantity = str(
                    round(max(symbol.position_amount / (chwidth * symbol.stop_lose_ratio), symbol.min_qty),
                          symbol.quantity_precision))
                position_side = "SHORT"

                symbol.take_profit_price = take_profit_price
                symbol.stop_lose_price = stop_lose_price
                symbol.quantity = quantity
                symbol.position_side = position_side

                self.change_leverage(symbol.trade_symbol, symbol.leverage)
                open_order_id = self.open_position_market(symbol.trade_symbol, position_side, quantity)
                o = {'X': "NEW"}
                self.orders[open_order_id] = o
                self.open_times += 1
                self.update_balance()
                self.log_open(symbol)
                symbol.open_order_id = open_order_id
                symbol.status = STATUS_PENDING_OPENING_ORDER
                info_logger.warning(f"status==>STATUS_PENDING_OPENING_ORDER")
                self.op_history_short[label] = f"sell symbol:{symbol.trade_symbol} p:{symbol.current_price} " \
                                               f"qty:{symbol.quantity} stop:{symbol.stop_lose_price} profit:{symbol.take_profit_price} " \
                                               f"t:{utils.timestamp_to_string(self.servertime / 1000)} break:{utils.timestamp_to_string(pl['t'] / 1000)}"

    def change_leverage(self, trade_symbol, leverage):
        try:
            response = self.um_futures_client.change_leverage(
                symbol=trade_symbol, leverage=leverage, recvWindow=6000
            )
            logging.info(response)
        except ClientError as error:
            logging.error(
                "Found error. status: {}, error code: {}, error message: {}".format(error.status_code, error.error_code,
                                                                                    error.error_message))

    def handle_pending_opening_order(self, symbol):
        order = None
        if symbol.open_order_id in self.orders.keys():
            order = self.orders[symbol.open_order_id]

        if order and order["X"] == "FILLED":
            symbol.stop_lose_order_id = self.post_stop_lose_order(symbol)
            symbol.take_profit_order_id = self.post_take_profit_order(symbol)
            symbol.status = STATUS_HOLDING
            info_logger.warning(f"status==>STATUS_HOLDING")
        else:
            o = self.query_order(symbol.trade_symbol, symbol.open_order_id)
            if o:
                self.orders[symbol.open_order_id]['X'] = o['status']

    def refresh_orders(self):
        now = int(time.time())
        internal = now - self.last_refresh_order_ts
        info_logger.warning(f"========== refresh_orders {internal} ==========")
        if now - self.last_refresh_order_ts > REFRESH_ORDER_INTERNAL:
            if self.symbol.open_order_id:
                if self.symbol.open_order_id not in self.orders.keys():
                    o = {'X': "NEW"}
                    self.orders[self.symbol.open_order_id] = o
                else:
                    o = self.orders[self.symbol.open_order_id]

                if o['X'] == "NEW":
                    o = self.query_order(self.symbol.trade_symbol, self.symbol.open_order_id)
                    if o:
                        self.orders[self.symbol.open_order_id]['X'] = o['status']

            if self.symbol.take_profit_order_id:
                if self.symbol.take_profit_order_id not in self.orders.keys():
                    o = {'X': "NEW"}
                    self.orders[self.symbol.take_profit_order_id] = o
                else:
                    o = self.orders[self.symbol.take_profit_order_id]

                if o['X'] == "NEW":
                    o = self.query_order(self.symbol.trade_symbol, self.symbol.take_profit_order_id)
                    if o:
                        self.orders[self.symbol.take_profit_order_id]['X'] = o['status']

            if self.symbol.stop_lose_order_id:
                if self.symbol.stop_lose_order_id not in self.orders.keys():
                    o = {'X': "NEW"}
                    self.orders[self.symbol.stop_lose_order_id] = o
                else:
                    o = self.orders[self.symbol.stop_lose_order_id]

                if o['X'] == "NEW":
                    o = self.query_order(self.symbol.trade_symbol, self.symbol.stop_lose_order_id)
                    if o:
                        self.orders[self.symbol.stop_lose_order_id]['X'] = o['status']

            self.last_refresh_order_ts = now

    def query_order(self, trade_symbol, orderId):
        try:
            o = self.um_futures_client.query_order(
                symbol=trade_symbol, orderId=orderId, recvWindow=2000
            )
            info_logger.info(o)
            return o
        except ClientError as error:
            info_logger.error(
                "Found error. status: {}, error code: {}, error message: {}".format(
                    error.status_code, error.error_code, error.error_message
                )
            )
            return None

    def post_stop_lose_order(self, symbol):
        info_logger.warning("========== post_stop_lose_order ==========")
        info_logger.warning(
            f"SELL current:{symbol.current_price} stop_lose_price:{symbol.stop_lose_price} quantity:{symbol.quantity} side:{symbol.position_side}")
        try:
            if symbol.position_side == "LONG":
                response = self.um_futures_client.new_order(
                    symbol=symbol.trade_symbol,
                    side="SELL",
                    type="STOP_MARKET",
                    stopPrice=symbol.stop_lose_price,
                    quantity=symbol.quantity,
                    positionSide=symbol.position_side,
                )
            else:
                response = self.um_futures_client.new_order(
                    symbol=symbol.trade_symbol,
                    side="BUY",
                    type="STOP_MARKET",
                    stopPrice=symbol.stop_lose_price,
                    quantity=symbol.quantity,
                    positionSide=symbol.position_side,
                )

            logging.info(response)
            return response['orderId']
        except ClientError as error:
            logging.error(
                "Found error. status: {}, error code: {}, error message: {}".format(error.status_code, error.error_code,
                                                                                    error.error_message))

    def post_take_profit_order(self, symbol):
        info_logger.warning("========== post_close_high_order ==========")
        info_logger.warning(
            f"Close current:{symbol.current_price} take_profit_price:{symbol.take_profit_price} quantity:{symbol.quantity} side:{symbol.position_side}")
        try:
            if symbol.position_side == "LONG":
                response = self.um_futures_client.new_order(
                    symbol=symbol.trade_symbol,
                    side="SELL",
                    type="LIMIT",
                    price=symbol.take_profit_price,
                    quantity=symbol.quantity,
                    positionSide=symbol.position_side,
                    timeInForce="GTC"
                )
            else:
                response = self.um_futures_client.new_order(
                    symbol=symbol.trade_symbol,
                    side="BUY",
                    type="LIMIT",
                    price=symbol.take_profit_price,
                    quantity=symbol.quantity,
                    positionSide=symbol.position_side,
                    timeInForce="GTC"
                )
            logging.info(response)
            return response['orderId']
        except ClientError as error:
            logging.error(
                "Found error. status: {}, error code: {}, error message: {}".format(error.status_code, error.error_code,
                                                                                    error.error_message))

    def handle_holding(self, symbol):
        if symbol.take_profit_order_id and symbol.take_profit_order_id in self.orders.keys():
            take_profit_order = self.orders[symbol.take_profit_order_id]
            if take_profit_order['X'] == "FILLED":
                info_logger.warning(" 止盈订单已触发，取消止损订单 ")
                self.win_times += 1
                self.update_balance()
                self.log_win(symbol)
                self.cancel_order(symbol, symbol.stop_lose_order_id)
                symbol.status = STATUS_NO_POSITION
                symbol.open_order_id = None
                symbol.stop_lose_order_id = None
                symbol.take_profit_order_id = None
                info_logger.warning(f"status==>STATUS_NO_POSITION")

        if symbol.stop_lose_order_id and symbol.stop_lose_order_id in self.orders.keys():
            stop_lose_order = self.orders[symbol.stop_lose_order_id]
            if stop_lose_order['X'] == "FILLED":
                info_logger.warning(" 止损订单已触发，取消止盈订单 ")
                self.lose_times += 1
                self.update_balance()
                self.log_lose(symbol)
                self.cancel_order(symbol, symbol.take_profit_order_id)
                symbol.status = STATUS_NO_POSITION
                symbol.open_order_id = None
                symbol.stop_lose_order_id = None
                symbol.take_profit_order_id = None
                info_logger.warning(f"status==>STATUS_NO_POSITION")

    def update_balance(self):
        account = self.um_futures_client.account(recvWindow=6000)
        self.usdt_balance = float(account['totalWalletBalance'])

    def log_open(self, symbol):
        now = int(time.time())
        record_logger.info(f"{self.open_times}|{self.win_times}|{self.lose_times} {symbol.position_side} 开仓 {utils.timestamp_to_string(now)} 价:{symbol.current_price} "
                           f"盈:{symbol.take_profit_price} 损:{symbol.stop_lose_price} 量:{symbol.quantity} 余:{round(self.usdt_balance, 2)}")

    def log_win(self, symbol):
        now = int(time.time())
        record_logger.info(
            f"{self.open_times}|{self.win_times}|{self.lose_times} 止盈 {utils.timestamp_to_string(now)} {symbol.take_profit_order_id} 余:{round(self.usdt_balance, 2)}")
        record_logger.info("============================================================")

    def log_lose(self, symbol):
        now = int(time.time())
        record_logger.info(
            f"{self.open_times}|{self.win_times}|{self.lose_times} 止损 {utils.timestamp_to_string(now)} {symbol.stop_lose_order_id} 余:{round(self.usdt_balance, 2)}")
        record_logger.info("============================================================")

    def cancel_order(self, symbol, orderId):
        try:
            response = self.um_futures_client.cancel_order(
                symbol=symbol.trade_symbol, orderId=orderId, recvWindow=2000
            )
            logging.info(response)
        except ClientError as error:
            logging.error(
                "Found error. status: {}, error code: {}, error message: {}".format(
                    error.status_code, error.error_code, error.error_message
                )
            )

    def open_position_market(self, trade_symbol, positionSide, quantity):
        info_logger.warning(f"========== open_position_market ==========")
        info_logger.warning(
            f"trade_symbol:{trade_symbol} positionSide:{positionSide} quantity:{quantity} current:{self.symbol.current_price}")
        try:
            if positionSide == "LONG":
                response = self.um_futures_client.new_order(
                    symbol=trade_symbol,
                    side="BUY",
                    type="MARKET",
                    quantity=quantity,
                    positionSide=positionSide,
                )
            else:
                response = self.um_futures_client.new_order(
                    symbol=trade_symbol,
                    side="SELL",
                    type="MARKET",
                    quantity=quantity,
                    positionSide=positionSide,
                )
            logging.info(response)
            orderId = response['orderId']
            return orderId
        except ClientError as error:
            logging.error(
                "Found error. status: {}, error code: {}, error message: {}".format(error.status_code, error.error_code,
                                                                                    error.error_message))

    def handle_unknown_error(self, symbol):
        info_logger.warning(f"UNKNOWD ERROR {symbol.trade_symbol}")

    def log_status(self):
        info_logger.warning(f"s:{self.symbol.trade_symbol} p:{self.symbol.current_price}")
        info_logger.warning(f"********** STATUS {self.symbol.status} **********")
        info_logger.warning("********** pivot high **********")
        for ph in self.symbol.pivot_high:
            is_entry = ph['is_break'] and ph['test_times'] >= self.symbol.min_number_of_test
            if not ph["is_break"]:
                # if not ph["is_break"]:
                info_logger.warning(
                    f"{utils.timestamp_to_string(ph['t'] / 1000)} h:{ph['h']} lb:{ph['low_band']} t:{ph['test_times']} b:{ph['is_break']} cw:{ph['chwidth']} {'====== ie ======' if is_entry else ''}")

        info_logger.warning("********** pivot low **********")
        for pl in self.symbol.pivot_low:
            is_entry = pl['is_break'] and pl['test_times'] >= self.symbol.min_number_of_test
            if not pl["is_break"]:
                # if not pl["is_break"]:
                info_logger.warning(
                    f"{utils.timestamp_to_string(pl['t'] / 1000)} l:{pl['l']} hb:{pl['high_band']} t:{pl['test_times']} b:{pl['is_break']} cw:{pl['chwidth']} {'====== ie ======' if is_entry else ''}")

        info_logger.warning("********** LONG history **********")
        for key in self.op_history_long.keys():
            info_logger.warning(self.op_history_long[key])

        info_logger.warning("********** SHORT history **********")
        for key in self.op_history_short.keys():
            info_logger.warning(self.op_history_short[key])

        info_logger.warning("********** POSITION **********")
        if self.position:
            info_logger.warning(
                f"{self.position.symbol} {self.position.entryPrice} {self.position.positionAmt} {self.position.positionSide} ")

        info_logger.warning("********** ORDERS **********")
        if self.symbol.open_order_id and self.symbol.open_order_id in self.orders.keys():
            info_logger.warning(f"open order:{self.symbol.open_order_id} {self.orders[self.symbol.open_order_id]['X']}")

        if self.symbol.stop_lose_order_id and self.symbol.stop_lose_order_id in self.orders.keys():
            info_logger.warning(
                f"stop lose order:{self.symbol.stop_lose_order_id} {self.orders[self.symbol.stop_lose_order_id]['X']}")

        if self.symbol.take_profit_order_id and self.symbol.take_profit_order_id in self.orders.keys():
            info_logger.warning(
                f"take profit order:{self.symbol.take_profit_order_id} {self.orders[self.symbol.take_profit_order_id]['X']}")

    def update_server_time(self):
        # info_logger.warning("********** update_server_time **********")
        self.servertime = self.um_futures_client.time()['serverTime']

    def has_stop_file(self):
        return os.path.exists(STOP_FILE)

    @logger.catch
    def run(self):
        try:
            self.on_init()
            while True:
                sleep(INTERVAL_S)
                # self.lock.acquire()
                self.on_ticker()
                # self.lock.release()
        except Exception as e:
            now = int(time.time())
            traceback.print_exc()
            # 或者得到堆栈字符串信息
            info = traceback.format_exc()
            info_logger.warning("quant运行报错 %s,%s" % (utils.timestamp_to_string(time.time()), str(info)))
            self.restart()
            raise e

    def restart(self):
        # info_logger.warning("restart")
        # os.system("./r.sh")
        pass

    def kill(self):
        info_logger.warning("stop running")
        os.system("./kill.sh")

    def exit(self):
        sys.exit(1)

    def umFuturesWebsocketClientHandler(self, _, message):
        # info_logger.warning(f"===== umFuturesWebsocketClientHandler =====")
        msg = json.loads(message)
        if "e" not in msg.keys():
            return
        if msg['e'] == "24hrMiniTicker":
            self.update_mini_ticker(msg)
        elif msg['e'] == "ACCOUNT_UPDATE":
            self.update_account(msg)
        elif msg['e'] == "ORDER_TRADE_UPDATE":
            self.update_order(msg)
            pass
        else:
            info_logger.warning(msg)

    def update_account(self, msg):
        info_logger.warning("========== account update ==========")
        info_logger.warning(msg)
        position_list = msg['a']['P']
        for p in position_list:
            logging.warning(p)
            if p['s'] == self.symbol.trade_symbol:
                position = Position()
                position.symbol = p['s']
                position.positionSide = p['ps']
                position.positionAmt = p['pa']
                position.entryPrice = p['ep']
                self.position = position
        self.usdt_balance = float(msg['a']['B'][0]['wb'])

    def update_mini_ticker(self, msg):
        self.symbol.current_price = round(float(msg['c']), self.symbol.price_precision)

    def update_order(self, msg):
        info_logger.warning("========== update_order ==========")
        order = msg['o']
        orderId = msg['o']['i']
        self.orders[orderId] = order
        info_logger.warning(f"{orderId} ==> {order['X']}")


def main():
    quant = Quant()
    quant.run()


if __name__ == "__main__":
    main()
